<?php
/**
 * This is interface for storage via SSH storage engine
 * @package     nuo
 * @subpackage  nuo.storage
 * @author      Nam Nguyen <nam.ngvan@gmail.com>
 * @version     1.0
 * @since       Apr 19, 2013
 */
class Nuo_Storage_Ssh extends Nuo_Storage_Abstract
{
	/**
	 * @var resource
	 */
	protected $_sshResource = NULL;

	/**
	 * @var resource
	 */
	protected $_sftpResource = NULL;

	/**
	 * @var string
	 */
	protected $_host = 'localhost';

	/**
	 * @var int
	 */
	protected $_port = 22;

	/**
	 * @var string
	 */
	protected $_username = '';

	/**
	 * @var string
	 */
	protected $_password = '';

	/**
	 * @var string
	 */
	protected $_timeout = 20;

	/**
	 * @var string
	 */
	protected $_hostKey = '';

	/**
	 * @var string
	 */
	protected $_privateKey = '';

	/**
	 * @var string
	 */
	protected $_publicKey = '';

	/**
	 * constructor
	 * @param array $config
	 */
	function __construct($config)
	{
		extract($config, EXTR_SKIP);

		$this -> setId($id);
		$this -> setHost($host);
		$this -> setUsername($username);

		$this -> setBasePath($basePath);
		$this -> setBaseUrl($baseUrl);

		$this -> setPassword($password);
		$this -> setPort($port);
		$this -> setPublicKey($publicKey);
		$this -> setPrivateKey($privateKey);
		$this -> setHostKey($hostKey);
		$this -> setTimeout($timeout);
	}

	/**
	 * set host key
	 * @param string $host IP or domain name
	 * @return Storage_Ssh2
	 */
	public function setHost($host)
	{
		$this -> _host = $host;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getHost()
	{
		return $this -> _host;
	}

	/**
	 * set port
	 * @param int $port
	 * @return Storage_Ssh2
	 */
	public function setPort($port)
	{
		$port = (int)$port;
		if (!$port)
		{
			$port = 22;
		}
		$this -> _port = $port;
		return $this;
	}

	/**
	 * @return int
	 */
	public function getPort()
	{
		return $this -> _port;
	}

	/**
	 * @param string $username
	 * @return Storage_Ssh2
	 */
	public function setUsername($username)
	{
		$this -> _username = $username;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getUsername()
	{
		return $this -> _username;
	}

	/**
	 * @param string $password
	 * @return Storage_Ssh2
	 */
	public function setPassword($password)
	{
		$this -> _password = $password;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getPassword()
	{
		return $this -> _password;
	}

	/**
	 * @param int $timeout in seconds
	 * @return Storage_Ssh2
	 */
	public function setTimeout($timeout)
	{
		$timeout = (int)$timeout;

		if ($timeout < 20)
		{
			$timeout = 20;
		}

		$this -> _timeout = $timeout;

		return $this;
	}

	/**
	 * @return int
	 */
	public function getTimeout()
	{
		return $this -> _timeout;
	}

	/**
	 * @param string $privateKey
	 * @return Storage_Ssh2
	 */
	public function setPrivateKey($privateKey)
	{
		$this -> _privateKey = $privateKey;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getPrivateKey()
	{
		return $this -> _privateKey;
	}

	/**
	 * @param string $publicKey
	 * @return Storage_Ssh2
	 */
	public function setPublicKey($publicKey)
	{
		$this -> _publicKey = $publicKey;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getPublicKey()
	{
		return $this -> _publicKey;
	}

	/**
	 * @param string $hostKey
	 * @return Storage_Ssh2
	 */
	public function setHostKey($hostKey)
	{
		$this -> _hostKey = $hostKey;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getHostKey()
	{
		return $this -> _hostKey;
	}

	/**
	 * connect ftp resource handle
	 * @return void
	 */
	function connect()
	{
		if ($this -> _sshResource)
		{
			return;
		}

		$this -> _sshResource = ssh2_connect($this -> _host, $this -> _port);

		if (!$this -> _sshResource)
		{
			throw new Exception('Could not connect ssh  ' . $this -> _host . ' port: ' . $this -> _port);
		}

		if ($this -> _username && $this -> _password)
		{
			if (!ssh2_auth_password($this -> _sshResource, $this -> _username, $this -> _password))
			{
				throw new Exception('Could not authenticate ssh connection to host ' . $this -> _host);
			}
		}
		else
		if (!ssh2_auth_none($this -> _sshResource, $this -> _username))
		{
			throw new Exception('Could not authenticate without password ssh connection to host ' . $this -> _host);
		}

		if (NULL == $this -> _sshResource)
		{
			throw new Exception(strtr('could not connect to {host}:{port}', array(
				'{host}' => $this -> _host,
				'{port}' => $this -> _port
			)));
		}
	}

	/**
	 * disconnect session
	 * @return void
	 */
	function disconnect()
	{
		if ($this -> _stream and is_resource($this -> _stream))
		{
			@ftp_close($this -> _stream);
			$this -> _stream = NULL;
		}
	}

	/**
	 * @return resource
	 */
	function getSshResource()
	{
		$this -> _sshResource or $this -> connect();

		return $this -> _sshResource;
	}

	/**
	 * @see ssh2_sftp
	 * @return resource
	 */
	function getSftpResource()
	{
		if (null === $this -> _sftpResource)
		{
			$this -> _sftpResource = @ssh2_sftp($this -> getSshResource());

			if (null === $this -> _sftpResource)
			{
				throw new Exception('Could not create sftp resource');
			}
		}
		return $this -> _sftpResource;
	}

	/**
	 * @param string $command
	 * @param bool $silent_error optional default TRUE
	 * @return bool
	 * @throws Exception
	 */
	public function command($command)
	{
		$stream = @ssh2_exec($this -> getSshResource(), $command);

		if (!$stream)
		{
			throw new Exception(sprintf('Unable to execute command "%s"', $command));
		}
		$errorStream = @ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);

		stream_set_blocking($stream, true);
		stream_set_timeout($stream, $this -> getTimeout());
		if ($errorStream)
		{
			stream_set_blocking($errorStream, true);
			stream_set_timeout($errorStream, $this -> getTimeout());
		}

		$data = stream_get_contents($stream);
		$error = '';
		if ($errorStream)
		{
			$error = stream_get_contents($errorStream);
		}

		fclose($stream);
		if ($errorStream)
		{
			fclose($errorStream);
		}

		$this -> _lastError = $error;

		return trim($data);
	}

	/**
	 * copy content of relative path to local file and set mode
	 * @param string $path absolute file path
	 * @param string $local
	 * @param int mode
	 * @return bool
	 * @throws Exception
	 */
	public function get($path, $local)
	{

		$remote = $this -> getPath($path);

		// Error
		if (!@ssh2_scp_recv($this -> getResource(), $remote, $local))
		{
			throw new Exception(sprintf('Unable to get "%s" to "%s"', $path, $local));
		}

		return TRUE;
	}

	/**
	 * @param string $path relative path
	 * @param string $local  absolute local file path
	 * @return true|false
	 * @throws Exception
	 */
	function put($path, $local)
	{
		$remote = $this -> getPath($path);

		/**
		 * @TODO: IMPLEMENT NONE-BLOCKING SSH2 CONNECT.
		 */
		$return  = @ssh2_scp_send($this -> getResource(), $local, $remote, $this -> getUmask(0666));

		// Error
		if (!$return)
		{
			throw new Exception(sprintf('Unable to put "%s" to "%s"', $local, $remote));
		}
		return TRUE;
	}

	/**
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-unlink.php
	 * delete relative path
	 * @param string $path
	 * @return bool
	 * @throws Exception
	 */
	function delete($path)
	{
		$remote_file = $this -> getPath($path);

		if (!@ssh2_sftp_unlink($this -> getSftpResource(), $remote_file))
		{
			throw new Exception( array(
				'Could not delete file {file}',
				array('{file}' => $remote)
			));
		}
		return TRUE;
	}

	/**
	 * @param string $path relative path in database
	 * @return string|FALSE
	 * @throws Exception
	 */
	function getContents($path)
	{
		$remote_file = $this -> getPath($path);

		$contents = file_get_contents('ssh2.sftp://' . $this -> getSftpResource() . $path);

		if (FALSE === $contents)
		{
			throw new Exception(sprintf('Could not retry %s"', $remote_file));
		}
		return $contents;
	}

	/**
	 * XXX implement Storage_Sssh::putContens
	 * @param string $path relative path in database
	 * @param string $data
	 * @return string|FALSE
	 * @throws Exception
	 */
	function putContents($path, $data)
	{

	}

	public function isFile($path)
	{
	}

	/**
	 * @TODO: need to implement
	 * @param string $path
	 */
	public function isDir($path)
	{

	}

	/**
	 * @TODO: need to implement
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-mkdir.php
	 * @param string $path
	 * @param $mode
	 * @return bool
	 * @throws Exception
	 */
	function makeDir($path, $mode = 0755)
	{

	}

	/**
	 * @TODO: need to implement
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-rmdir.php
	 * @param string $path
	 * @return bool
	 * @throws Exception
	 */
	function removeDir($path)
	{
		$remote = $this -> getPath($relative_path);
	}

}
